﻿using FreeRedis;
using Common.Exceptions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Common.Cache
{
    /// <summary>
    /// FreeRedis客户端
    /// </summary>
    public class FreeRedisCache : IRedisCache
    {
        /// <summary>
        /// 配置Redis连接字符串
        /// </summary>
        public static Func<string, string> ConfigRedis;
        private static FreeRedisCache _default;
        /// <summary>
        /// redis实例
        /// </summary>
        public static FreeRedisCache Instance
        {
            get
            {
                if (_default == null)
                {
                    string server = ConfigRedis?.Invoke("redis");
                    if (string.IsNullOrEmpty(server))
                    {
                        throw new MissingConfigurationException("redis");
                    }
                    _default = new FreeRedisCache(server);
                }
                return _default;
            }
        }
        private RedisClient redisCli;
        /// <summary>
        /// redis连接字符串
        /// </summary>
        public string ConnectionString { get; private set; }
        /// <summary>
        /// 初始化Redis
        /// </summary>
        /// <param name="connection"></param>
        public FreeRedisCache(string connection)
        {
            Initialize(connection);
        }
        private void Initialize(string connection)
        {
            if (redisCli == null || ConnectionString != connection)
            {
                redisCli = new RedisClient(connection);
                redisCli.Serialize = val => JsonConvert.SerializeObject(val);
                redisCli.Deserialize = (json, type) => JsonConvert.DeserializeObject(json, type);
                redisCli.DeserializeRaw = (buffer, type) => JsonConvert.DeserializeObject(Encoding.UTF8.GetString(buffer), type);
                ConnectionString = connection;
            }
        }
        /// <inheritdoc/>
        public bool Add(string key, object value, TimeSpan? expiry = null)
        {
            int timeout = expiry.HasValue ? (int)expiry.Value.TotalSeconds : -1;
            redisCli.Set(key, value, timeout);
            return true;
        }
        /// <inheritdoc/>
        public bool Delete(string key)
        {
            redisCli.Del(key);
            return true;
        }
        /// <inheritdoc/>
        public bool Exist(string key)
        {
            return redisCli.Exists(key);
        }
        /// <inheritdoc/>
        public T Get<T>(string key)
        {
            return redisCli.Get<T>(key);
        }
        /// <inheritdoc/>
        public string[] GetKeys(string pattern)
        {
            var keys = redisCli.Keys(pattern);
            return redisCli.MGet(keys);
        }
        /// <summary>
        /// 此方法未实现
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="pattern"></param>
        /// <returns></returns>
        [Obsolete("this method does not work properly, and will be deleted some day in the future!")]
        public List<T> GetList<T>(string pattern)
        {
            var keys = redisCli.Keys(pattern);
            var val = redisCli.MGet<T>(keys);
            return val.ToList();
        }
        /// <inheritdoc/>
        public object GetObject(string key)
        {
            return GetString(key);
        }
        /// <inheritdoc/>
        public T GetOrAdd<T>(string key, Func<string, T> func, TimeSpan? expiry = null)
        {
            string val = GetString(key);
            if (!string.IsNullOrEmpty(val))
            {
                return ConvertTo<T>(val);
            }
            T funcVal = func(key);
            if (funcVal != null)
            {
                Add(key, funcVal, expiry);
            }
            return funcVal;
        }
        /// <inheritdoc />
        public T GetOrAdd<T>(string key, Func<string, JCacheEntry<T>> func)
        {
            string cacheValue = GetString(key);
            if (!string.IsNullOrEmpty(cacheValue))
            {
                return ConvertTo<T>(cacheValue);
            }
            T valueOfT = default;
            var entry = func(key);
            if (entry != null && entry.Data != null)
            {
                valueOfT = entry.Data;
                Add(key, entry.Data, entry.Expiry);
            }
            return valueOfT;
        }
        /// <inheritdoc/>
        public string GetString(string key)
        {
            return redisCli.Get(key);
        }
        /// <inheritdoc/>
        public string HashGet(string key, string field)
        {
            return redisCli.HGet(key, field);
        }
        /// <inheritdoc/>
        public T HashGet<T>(string key, string field)
        {
            return redisCli.HGet<T>(key, field);
        }
        /// <inheritdoc/>
        public bool HashSet(string key, string field, object value, TimeSpan? expiry = null)
        {
            redisCli.HSet(key, field, value);
            if (expiry.HasValue)
            {
                redisCli.Expire(key, (int)expiry.Value.TotalSeconds);
            }
            return true;
        }
        /// <inheritdoc/>
        public bool HashSet<T>(string key, IDictionary<string, T> keyValues)
        {
            var arg = new Dictionary<string, T>();
            foreach (var item in keyValues)
            {
                arg.Add(item.Key, item.Value);
            }
            redisCli.HSet(key, arg);
            return true;
        }
        /// <inheritdoc/>
        public bool Refresh(string key, object value = null, TimeSpan? expiry = null)
        {
            Delete(key);
            if (value != null)
            {
                return Add(key, value, expiry);
            }
            return true;
        }
        private static T ConvertTo<T>(object value)
        {
            Type t = typeof(T);
            if (t == typeof(string) || t.IsPrimitive)
            {
                return (T)Convert.ChangeType(value, t);
            }
            else
            {
                return JsonConvert.DeserializeObject<T>(value.ToString());
            }
        }
        /// <inheritdoc/>
        public IDistributedLock Lock(string key, int timeout, bool autoDelay = true)
        {
            var result = redisCli.Lock(key, timeout, autoDelay);
            if (result == null)
            {
                throw new LockTimeoutException("服务器忙，请稍后重试！");
            }
            return new FreeRedisDistributedLock(result);
        }
        /// <inheritdoc/>
        public object Eval(string script, string[] keys, params string[] parameters)
        {
            return redisCli.Eval(script, keys, parameters);
        }
        /// <inheritdoc/>
        public bool KeyExpire(string key, TimeSpan expiry)
        {
            return redisCli.Expire(key, (int)expiry.TotalSeconds);
        }
        /// <inheritdoc/>
        public IRedisCache Switch(int dbIndex)
        {
            var redis = new FreeRedisCache(ConnectionString);
            redis.redisCli.Select(dbIndex);
            return redis;
        }
        /// <inheritdoc/>
        public bool MSet<T>(Dictionary<string, T> keyValues, TimeSpan? expiry)
        {
            redisCli.MSet(keyValues);
            if (expiry.HasValue)
            {
                foreach (var kv in keyValues)
                {
                    KeyExpire(kv.Key, expiry.Value);
                }
            }
            return true;
        }
    }
}
